package com.starview.platform.resource.catalog;

import com.starview.platform.data.type.Location;
import com.starview.platform.data.value.ResourceURI;
import com.starview.platform.resource.ResourceException;
import com.starview.platform.resource.Resources;
import com.starview.platform.resource.URIUtils;
import com.starview.star.StarCompiler;
import com.starview.star.StarRules;
import com.starview.star.code.repository.CodeRepository;
import com.starview.star.compiler.CompilerUtils;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.ast.Abstract;
import com.starview.star.compiler.ast.IAbstract;
import com.starview.star.compiler.ast.StringLiteral;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.Reader;

public class CatalogUtils{
  public static final String CATALOG = "catalog";

  public static Catalog parseCatalog(ResourceURI uri, Catalog fallback)
          throws CatalogException, ResourceException{
    if (uri.getScheme().equals(Resources.STDSCHEME) && uri.getPath().equals(CATALOG))
      return StarRules.starCatalog();
    else {
      try (Reader reader = Resources.getReader(uri)) {
        String src = StringUtils.readFileIntoString(reader);
        return parseCatalog(uri, URIUtils.getPackageName(uri), src, fallback);
      } catch (IOException e) {
        throw new CatalogException(e.getMessage());
      }
    }
  }

  public static Catalog parseCatalog(ResourceURI uri, String name, String src,
                                     Catalog fallback) throws CatalogException{
    ErrorReport errors = new ErrorReport();

    IAbstract parse = StarCompiler.parseString(src, Location.location(uri), errors);

    if (!errors.isErrorFree())
      throw new CatalogException("error: " + errors);
    else {
      ResourceURI baseURI = null;

      if (Abstract.isBinary(parse, StandardNames.IS) && CompilerUtils.isBraceTerm(Abstract.binaryRhs(parse), CATALOG)) {
        IAbstract content = CompilerUtils.braceArg(Abstract.binaryRhs(parse));
        while (Abstract.isBinary(content, StandardNames.TERM)) {
          IAbstract lhs = Abstract.binaryLhs(content);
          if (Abstract.isBinary(lhs, StandardNames.IS) && Abstract.isIdentifier(Abstract.binaryLhs(lhs), "baseURI")) {
            try {
              baseURI = ResourceURI.parseURI(Abstract.getString(Abstract.binaryRhs(lhs)) + "/catalog");
            } catch (ResourceException e) {
              errors.reportError("invalid base URI\nbecause " + e.getMessage(), content.getLoc());
            }
            content = Abstract.binaryRhs(content);
          }
        }
        if (baseURI == null)
          baseURI = uri;
        Catalog cat = new MemoryCatalog(name, baseURI, fallback);
        if (Abstract.isBinary(content, StandardNames.IS) || Abstract.isBinary(content, StandardNames.EQUAL)) {
          if (Abstract.isIdentifier(Abstract.binaryLhs(content), "content"))
            content = Abstract.binaryRhs(content);
          else
            errors.reportError("expecting 'content = hash{}'", content.getLoc());
        }
        if (CompilerUtils.isBraceTerm(content, StandardNames.HASH)) {
          for (IAbstract row : CompilerUtils.unWrap(CompilerUtils.braceArg(content), StandardNames.TERM)) {
            if (Abstract.isBinary(row, StandardNames.MAP_ARROW)) {
              IAbstract key = Abstract.binaryLhs(row);
              IAbstract val = Abstract.binaryRhs(row);
              if (key instanceof StringLiteral) {
                String keyName = ((StringLiteral) key).getLit();
                try {
                  if (Abstract.isBinary(val, StandardNames.AS) && Abstract.isIdentifier(Abstract.binaryRhs(val), "uri")
                          && Abstract.binaryLhs(val) instanceof StringLiteral) {
                    cat.addEntry(keyName, ResourceURI.parseURI(((StringLiteral) Abstract.binaryLhs(val)).getLit()));
                  } else if (val instanceof StringLiteral) {
                    cat.addEntry(keyName, ResourceURI.parseURI(((StringLiteral) val).getLit()));
                  }
                } catch (ResourceException e) {
                  errors.reportError("invalid URI\nbecause " + e.getMessage(), row.getLoc());
                }
              } else
                errors.reportError("expecting \"name\", not " + key, key.getLoc());
            } else
              errors.reportError("expecting: \"name\"->\"uri\"", row.getLoc());
          }
          return cat;
        } else
          errors.reportError("invalid catalog contents " + content, content.getLoc());
      } else
        errors.reportError("expecting catalog definition, not " + parse, parse.getLoc());
      // there have been errors, so we only instantiate a catalog here to have something to return
      Catalog cat = new MemoryCatalog(name, baseURI, fallback);
      if (!errors.isErrorFree())
        throw new CatalogException("error: " + errors);
      return cat;
    }
  }

  public static Catalog catalogInDirectory(ResourceURI uri, File dir, Catalog fallback)
          throws CatalogException{
    File catFile = dir;
    if (catFile.isDirectory())
      catFile = new File(catFile, CATALOG);
    if (catFile.canRead()) {
      try {
        String catContent = StringUtils.readFileIntoString(catFile);
        return parseCatalog(uri, dir.getName(), catContent, fallback);
      } catch (IOException e) {
        throw new CatalogException(e.getMessage());
      }
    }

    return new NullCatalog();
  }

  public static Catalog catalogInDirectory(ResourceURI uri, Catalog fallback, CodeRepository repository)
          throws CatalogException{
    if (uri.getScheme().equals("file")) {
      String path = uri.getPath();
      if (path.indexOf('/') > 0)
        return catalogInDirectory(uri, new File(path.substring(0, path.lastIndexOf('/'))), fallback);
      else
        return new NullCatalog();
    } else
      return new NullCatalog();
  }
}
